# 对接bcos串口，传输文件
import os
import sys
import argparse
from time import time_ns
import serial

def list_hex(list) -> str:
    ret = ''

    for i in list:
        temp = hex(i)
        if len(temp) < 4:
            ret = ret + '0x0' + temp[2:].upper() + ' '
        else:
            ret = ret + '0x' + temp[2:].upper() + ' '

    return ret.strip()

def crc16(buff):
    crc16 = 0
    polynomial = 0x1021
    curval = 0

    for val in buff:
        curval = (val << 8) & 0xFFFF

        for i in range(8):
            if (crc16 ^ curval) & 0x8000:
                crc16 = ((crc16 << 1) & 0xFFFF) ^ polynomial
            else:
                crc16 = (crc16 << 1) & 0xFFFF
            curval = (curval << 1) & 0xFFFF
    
    return crc16

def cmd_pack(opcode, args = []):
    cmd = [0x24, opcode]
    
    Len = len(args)
    cmd.append(Len & 0xFF)
    cmd.append((Len >> 8) & 0xFF)
    cmd.extend(args)

    crc = crc16(cmd)
    cmd.append(crc & 0xFF)
    cmd.append((crc >> 8) & 0xFF)

    return cmd

def wait_cmd_reply(serialport:serial.Serial, timeout = 30):
    '''
    等待串口命令回复
    '''
    due = time_ns() + (timeout * 1000000)
    while time_ns() < due:
        if serialport.in_waiting > 0:
            reply = serialport.read(serialport.in_waiting)
            # print(list_hex(reply))
            for i in range(len(reply)):
                if reply[i] == 0x24:
                    Len = reply[i + 2] | (reply[i + 3] << 8)
                    if len(reply) - i >= Len:
                        reply = reply[i: (i + Len + 6)]
                        break
            # print(list_hex(reply))
            crc = crc16(reply[:-2])
            crc_terminal = (reply[-1] << 8) | reply[-2]
            # print('crc:', hex(crc))
            if reply[0] == 0x24 and crc == crc_terminal:
                opcode = reply[1]
                status = reply[4]
                param = None
                if len(reply) > 6:
                    param = reply[5:-2]
                return opcode, status, param
    
    return None, None, None

if __name__ == '__main__':
    #获取参数
    parser = argparse.ArgumentParser(description = '加载文件到bcos的文件系统.')
    parser.add_argument('-serial', help='串口名:COMx.')
    parser.add_argument('-inpath', help='要传输的文件在本地的路径。')
    parser.add_argument('-tpath', help='文件在bcos中的路径。')

    args = parser.parse_args()

    filepath = args.inpath
    filename = args.tpath
    com_name = args.serial

    if len(filename) == 0 or len(filepath) == 0:
        print('请入传输文件路径或存储路径')
        sys.exit(0)

    # 打开串口    
    serialport = serial.Serial(com_name, 115200, timeout = 60)

    cmd = [ord(item) for item in 'recvmod']
    cmd.append(0x0D)
    print(list_hex(cmd))
    serialport.write(cmd)

    # 等待回复
    opcode, status, param = wait_cmd_reply(serialport, 1000)
    print(hex(opcode), hex(status))
    if opcode == 0x66 and status == 0x00:
        print('模式切换成功')
        # 设置文件路径
        cmd = cmd_pack(0x14, [ord(item) for item in filename])
        print(list_hex(cmd))
        serialport.write(cmd)

        # 等待回复
        opcode, status, param = wait_cmd_reply(serialport)
        if opcode != 0x14 or status != 0x00:
            print('文件路径设置失败')
            sys.exit()

        print('路径设置成功')
        bfd = open(filepath, 'rb')
        size = os.path.getsize(filepath)
        data = bfd.read(size)
        bfd.close()

        # 传输文件数据
        index = 0
        while index < size:
            cmd = []
            if index + 512 < size:
                cmd = cmd_pack(0x12, data[index:(index + 512)])
                index = index + 512
            else:
                cmd = cmd_pack(0x12, data[index:])
                index = size
            # print(list_hex(cmd))
            serialport.write(cmd)
            opcode, status, param = wait_cmd_reply(serialport, 20000)
            if opcode != 0x12 or status != 0x00:
                print('文件传输失败')
                sys.exit(0)

        print('文件传输成功')
    # 退出recvmod
    cmd = cmd_pack(0x10)
    print(list_hex(cmd))
    serialport.write(cmd)
    opcode, status, param = wait_cmd_reply(serialport, 1000)
    if opcode != 0x10 or status != 0x00:
        print('Bcos系统终端退出文件传输模式失败,请复位或重启才可使用终端功能。')
        sys.exit(0)
    print('Bcos系统终端退出文件传输模式成功。')
